home *** CD-ROM | disk | FTP | other *** search
/ FishMarket 1.0 / FishMarket v1.0.iso / fishies / 476-500 / disk_497 / nlcalc / source / cfunc.c < prev    next >
C/C++ Source or Header  |  1992-05-06  |  16KB  |  549 lines

  1. /*
  2.  *  CALC      Provides a calculator that opens on the active screen when
  3.  *            you press a specific key sequence.  Otherwise, the program
  4.  *            waits quitely in the background.
  5.  *
  6.  *              Copyright 1989 by Davide P. Cervone.
  7.  *  You may use this code, provided this copyright notice is kept intact.
  8.  */
  9.  
  10. #define INTUITION_PREFERENCES_H         /* don't need 'em */
  11. #include <intuition/intuition.h>
  12. #include <proto/intuition.h>
  13. #include <proto/dos.h>
  14. #include <proto/graphics.h>
  15. #include <proto/exec.h>
  16.  
  17. #include "cHandler.h"
  18. #include "cKeys.h"
  19.  
  20. /*
  21.  *  ASCII codes for some special keys
  22.  */
  23.  
  24. #define CTRLC       3
  25. #define BACKSPACE   8
  26. #define ENTER       13
  27.  
  28. /*
  29.  *  Delay() time for keypress to invert corresponding gadget
  30.  */
  31.  
  32. #define KEYPAUSE    4L
  33.  
  34. /*
  35.  *  TINYVALUE is the round-off value for the display.
  36.  *  BIGVALUE is the largest value that can be dispayed or entered.
  37.  */
  38.  
  39. #define NODECIMAL   -1
  40. #define TINYVALUE   0.000000000001
  41. #define BIGVALUE    999999999999.0
  42.  
  43. /*
  44.  *  MAXDISPLAY is the number of characters that are in the display.
  45.  *  MAXSTACK is the size of the pending operation stack (includes
  46.  *  parenthesies).
  47.  */
  48.  
  49. #define MAXDISPLAY  13
  50. #define MAXSTACK    20
  51.  
  52.  
  53. /*
  54.  *  Standard character width for Topaz 8-pt font
  55.  */
  56.  
  57. #define CHARW       8
  58.  
  59.  
  60. static struct IntuiText *DisplayIText = &KeyText[KEY_DISP];
  61.  
  62. static short DisplayPos;                /* The position in the DisplayBuffer */
  63. static double DisplayValue;             /* The value on the display */
  64. static double DecimalPower;             /* The power of ten for next digit */
  65. static int DecimalPlace = NODECIMAL;    /* Decimal place for next digit */
  66. static int NoDigit = TRUE;              /* TRUE if digits not yet entered */
  67. static int ErrorStatus;                 /* TRUE if an error occured */
  68.  
  69. /*
  70.  *  The codes for each of the pending operations that can appear on the stack
  71.  */
  72.  
  73. #define OP_NONE     0
  74. #define OP_PLUS     1
  75. #define OP_MINUS    2
  76. #define OP_TIMES    3
  77. #define OP_DIVIDE   4
  78. #define OP_LPAREN   5
  79. #define OP_RPAREN   6
  80. #define OP_EQUAL    7
  81. #define OP_PAREN    8
  82.  
  83.  
  84. /*
  85.  *  The precedence values for each of the stack operations.
  86.  */
  87.  
  88. static UBYTE Precedence[] = {0,4,4,5,5,6,2,1,3};
  89. static UBYTE Op[MAXSTACK];          /* The pending operation stack */
  90. static double Val[MAXSTACK];        /* The pending value stack */
  91. static short StackTop;              /* pointer to top of stack */
  92.  
  93.  
  94. /*
  95.  *  ClearDisplay()
  96.  *
  97.  *  Clears the display buffer and sets all the flags to indicate that we
  98.  *  are now entering a number from the keypad.
  99.  */
  100.  
  101. static void ClearDisplay()
  102. {
  103.    NoDigit = FALSE;
  104.    DecimalPlace = NODECIMAL;
  105.    DecimalPower = 0.0;
  106.    DisplayValue = 0.0;
  107.    DisplayPos = 1;
  108.    DisplayBuffer[DisplayPos] = '0';
  109.    DisplayBuffer[DisplayPos+1] = 0;
  110.    DisplayIText->IText = &DisplayBuffer[1];
  111.    DisplayIText->LeftEdge = DISPLAYIW - CHARW;
  112.    ErrorStatus = FALSE;
  113. }
  114.  
  115.  
  116. /*
  117.  *  RefreshDisplay()
  118.  *
  119.  *  Refresh the DisplayGadget
  120.  */
  121.  
  122. static void RefreshDisplay()
  123. {
  124.    RefreshGList(&CalcGadget[KEY_DISP],CalcWindow,NULL,1L);
  125. }
  126.  
  127.  
  128. /*
  129.  *  DisplayError()
  130.  *
  131.  *  Put the error message in the display buffer, and set the display
  132.  *  gadget's left edge so that the message is centered.  Refresh the
  133.  *  display.  Set the error status flag, and clear the stack.
  134.  */
  135.  
  136. static void DisplayError(s)
  137. char *s;
  138. {
  139.    ClearDisplay();
  140.    NoDigit = TRUE;
  141.    strcpy(DisplayIText->IText,s);
  142.    DisplayIText->LeftEdge = (DISPLAYIW-strlen(DisplayIText->IText)*CHARW+1) / 2;
  143.    RefreshDisplay();
  144.    ErrorStatus = TRUE;
  145.    StackTop = 0;
  146. }
  147.  
  148.  
  149. /*
  150.  *  AddToDisplay()
  151.  *
  152.  *  Check for buffer overflow.  If none, then add character into the display
  153.  *  buffer and adjust the display position (but don't add non-significant
  154.  *  zero digits).  Refresh the display on the screen and cancel any errors.
  155.  */
  156.  
  157. static int AddToDisplay(c)
  158. char c;
  159. {
  160.    if (DisplayPos == MAXDISPLAY ||
  161.        DisplayValue > BIGVALUE || DisplayValue < -BIGVALUE)
  162.    {
  163.       DisplayError("Overflow");
  164.    } else {
  165.       if (DisplayValue != 0.0 || DisplayBuffer[DisplayPos] != '0' ||
  166.           DecimalPlace != NODECIMAL) DisplayIText->LeftEdge -= CHARW;
  167.       DisplayBuffer[DisplayPos++] = c;
  168.       DisplayBuffer[DisplayPos] = 0;
  169.       if (DisplayValue == 0.0 && c == '0' && DecimalPlace == NODECIMAL)
  170.          DisplayPos--;
  171.       RefreshDisplay();
  172.       ErrorStatus = FALSE;
  173.    }
  174.    return(ErrorStatus == FALSE);
  175. }
  176.  
  177.  
  178. /*
  179.  *  SetDisplay()
  180.  *
  181.  *  Sets the display to the given value.
  182.  *  Check for overflow.  If none, then
  183.  *  Set the display value and the current display position.
  184.  *  If the value is near zero, set the display to zero,
  185.  *  Otherwise
  186.  *    If the value is negative, add a minus sign and make the value positive.
  187.  *    divide the value by the power of ten that makes its integer part only
  188.  *       one digit and add a small offset so that we round up.
  189.  *    If the number is less than 1.0, then add '0.' and as many zeros as
  190.  *       needed to get to the first non-zero decimal digit.
  191.  *    Mark the last non-zero decimal place.
  192.  *    As long as the value is still non-zero,
  193.  *      Find the first digit of the number (TINYVALUE is for round-off errors)
  194.  *      Multiply by ten to get next digit ready.
  195.  *      Add a decimal place if it is time.
  196.  *      Check if the digit is non-zero (or a non-trivial zero)
  197.  *      Decrement the digits still before the decimal place.
  198.  *      If the display is filled, cancel the loop.
  199.  *  NULL-terminate the display string.
  200.  *  Calculate the display offset, and refresh the display.
  201.  *  Set the flags.
  202.  */
  203.  
  204. static void SetDisplay(v)
  205. double v;
  206. {
  207.    char c;
  208.    short digits,LastNonZero;
  209.    extern double pow(),log10();
  210.  
  211.    if (v > BIGVALUE || v < -BIGVALUE)
  212.    {
  213.       DisplayError("Overflow");
  214.    } else {
  215.       DisplayValue = v;
  216.       DisplayPos = 1;
  217.       DisplayIText->IText = &DisplayBuffer[1];
  218.       if (v <= TINYVALUE && v >= -TINYVALUE)
  219.       {
  220.          DisplayBuffer[DisplayPos++] = '0';
  221.          DisplayBuffer[DisplayPos++] = '.';
  222.          LastNonZero = DisplayPos;
  223.       } else {
  224.          if (v < 0.0)
  225.          {
  226.             DisplayBuffer[DisplayPos++] = '-';
  227.             v = -v;
  228.          }
  229.          digits = log10(v);
  230.          v = v / pow(10.0,(double)digits) + (5.0*TINYVALUE);
  231.          if (digits < 0)
  232.          {
  233.             DisplayBuffer[DisplayPos++] = '0';
  234.             DisplayBuffer[DisplayPos++] = '.';
  235.             LastNonZero = DisplayPos;
  236.             while (++digits) DisplayBuffer[DisplayPos++] = '0';
  237.             digits = -1;
  238.          } else {
  239.             LastNonZero = DisplayPos;
  240.          }
  241.          while (v > TINYVALUE)
  242.          {
  243.             c = v + TINYVALUE; v = (v-c) * 10.0;
  244.             DisplayBuffer[DisplayPos++] = c + '0';
  245.             if (digits == 0 && DisplayPos <= MAXDISPLAY)
  246.                DisplayBuffer[DisplayPos++] = '.';
  247.             if (c || digits >= 0) LastNonZero = DisplayPos;
  248.             digits--;
  249.             if (DisplayPos > MAXDISPLAY) v = 0.0;
  250.          }
  251.       }
  252.       DisplayBuffer[LastNonZero] = 0;
  253.       DisplayIText->LeftEdge = DISPLAYIW - (LastNonZero-1)*CHARW;
  254.       RefreshDisplay();
  255.       ErrorStatus = FALSE;
  256.       NoDigit = TRUE;
  257.       DecimalPlace = NODECIMAL;
  258.    }
  259. }
  260.  
  261.  
  262. /*
  263.  *  ClearEntry()
  264.  *
  265.  *  Clear the display.  Set the NoDigit flag to TRUE so that the zero we are
  266.  *  now displaying will not be part of the displayed number.
  267.  */
  268.  
  269. static void ClearEntry()
  270. {
  271.    ClearDisplay();
  272.    NoDigit = TRUE;
  273.    RefreshDisplay();
  274. }
  275.  
  276.  
  277. /*
  278.  *  DoFunction()
  279.  *
  280.  *  Do the operation that's on the stack using the value on the top of the
  281.  *  stack as the first operand and the display value as the second one.
  282.  *  (Check for division by zero errors)
  283.  */
  284.  
  285. static void DoFunction()
  286. {
  287.    switch(Op[StackTop])
  288.    {
  289.       case OP_PLUS:
  290.          DisplayValue = Val[StackTop] + DisplayValue;
  291.          break;
  292.  
  293.       case OP_MINUS:
  294.          DisplayValue = Val[StackTop] - DisplayValue;
  295.          break;
  296.  
  297.       case OP_TIMES:
  298.          DisplayValue = Val[StackTop] * DisplayValue;
  299.          break;
  300.  
  301.       case OP_DIVIDE:
  302.          if (DisplayValue != 0.0)
  303.             DisplayValue = Val[StackTop] / DisplayValue;
  304.            else
  305.             DisplayError("Zero Division");
  306.          break;
  307.    }
  308. }
  309.  
  310.  
  311. /*
  312.  *  DoOperation()
  313.  *
  314.  *  Perform any pending operations of higher (or equal precedence) up to 
  315.  *  an open parenthesis (the bottom of the stack is OP_NONE).
  316.  *  Convert a left-paren to OP_PAREN (the left paren has highest precedence
  317.  *    so it will not cause other operations to be performed, but when on
  318.  *    the stack, we want it to have the lowest precedence, so it will not be
  319.  *    poped until a right paren or an equal.
  320.  *  If the operation is not an equal or right paren (precedence 1 and 2) then
  321.  *    Push the operation on the stack (we need to get the next operand)
  322.  *    Push the current display value onto the stack.
  323.  *    (If there is a stack overflow, beep the screen).
  324.  */
  325.  
  326. static void DoOperation(theOp)
  327. int theOp;
  328. {
  329.    while (Precedence[theOp] <= Precedence[Op[StackTop]] && theOp != OP_NONE)
  330.    {
  331.       if (Op[StackTop] == OP_PAREN && theOp != OP_EQUAL) 
  332.          theOp = OP_NONE;
  333.         else
  334.          DoFunction();
  335.       if (StackTop) StackTop--;
  336.    }
  337.  
  338.    if (theOp == OP_LPAREN) theOp = OP_PAREN;
  339.    if (Precedence[theOp] > 2)
  340.    {
  341.       if (StackTop < MAXSTACK-1)
  342.       {
  343.          Op[++StackTop] = theOp;
  344.          Val[StackTop]  = DisplayValue;
  345.       } else {
  346.          DisplayBeep(CalcScreen);
  347.       }
  348.    }
  349. }
  350.  
  351.  
  352. /*
  353.  *  DoGadget()
  354.  *
  355.  *  The gadget ID indicates which function has been pressed.
  356.  *  For a dot, if we don't already have a dot, then
  357.  *    If no other digits have been entered, clear the display value.
  358.  *    If the current value is zero, add a zero to the display.
  359.  *    Set the decimal place counters
  360.  *    Add a dot to the display.
  361.  *  For one of the function keys, parens, or equal,
  362.  *    Set NoDigit to indicate a calculated result, and clear the decimal flag.
  363.  *    Do the operation indicated by the gadget ID.
  364.  *    If an error did not occur, display the result.
  365.  *  For the sign change key,
  366.  *    If the number is a result, show it's negative,
  367.  *    Otherwise, if the displayalue is not zero, then 
  368.  *      If the displayed value is already negative, 
  369.  *        Change the IText pointer to be past the negative,
  370.  *        Fix the IText left edge,
  371.  *      Otherwise
  372.  *        Change the IText pointer to include the negative,
  373.  *        Fix the IText left edge,
  374.  *      Refresh the display.
  375.  *  For the Root key,
  376.  *    If the current value is negative, display an error.
  377.  *    otherwise display the square root.
  378.  *  For the Percent key,
  379.  *    If the number has been entered (not caluclated)
  380.  *      divide by 100
  381.  *      If the pending operation is + or -, then change display to 
  382.  *        the given percent of the pending operand.
  383.  *    Display the result.
  384.  *  For the Clear key,
  385.  *    Clear the stack of all pending operations,
  386.  *    and display the a zero.
  387.  *  For the Clear Entry key, clear the entry.
  388.  *  For a numeric key (the GadgetID tells which number was pressed),
  389.  *    If this is the first digit,
  390.  *      Clear the display area,
  391.  *      Set the display to the number that was pressed, and
  392.  *      Add the correct digit to the display.
  393.  *    Otherwise
  394.  *      If the value contains a decimal place,
  395.  *        increase the decimal power of the current digit position,
  396.  *        and increase the decimal place counter for the display.
  397.  *        Set the display value to the current value plus the number pressed
  398.  *           (shifted to the proper decimal place),
  399.  *        Add the digit to the display.
  400.  *      Otherwise (no decimal place yet)
  401.  *        multiply the current value by 10 and add the number pressed
  402.  *         Add the digit to the display.
  403.  */
  404.  
  405. void DoGadget(theGadget)
  406. struct Gadget *theGadget;
  407. {
  408.    if (theGadget)
  409.    {
  410.       switch(theGadget->GadgetID)
  411.       {
  412.          case KEY_DOT:
  413.             if (DecimalPlace == NODECIMAL)
  414.             {
  415.                if (NoDigit || DisplayValue == 0.0)
  416.                {
  417.                   ClearDisplay();
  418.                   DisplayPos++;
  419.                }
  420.                DecimalPlace = 0; 
  421.                DecimalPower = 1.0;
  422.                AddToDisplay('.');
  423.             }
  424.             break;
  425.  
  426.          case KEY_PLUS:
  427.          case KEY_MINUS:
  428.          case KEY_TIMES:
  429.          case KEY_DIVIDE:
  430.          case KEY_LPAREN:
  431.          case KEY_RPAREN:
  432.          case KEY_EQUAL:
  433.             NoDigit = TRUE;
  434.             DecimalPlace = NODECIMAL;
  435.             DoOperation(theGadget->GadgetID - KEY_PLUS + 1);
  436.             if (ErrorStatus == FALSE) SetDisplay(DisplayValue);
  437.             break;
  438.          
  439.          case KEY_SIGN:
  440.             if (NoDigit)
  441.             {
  442.                SetDisplay(-DisplayValue);
  443.             } else if (DisplayValue > TINYVALUE || DisplayValue < -TINYVALUE) {
  444.               if (DisplayIText->IText[0] == '-')
  445.               {
  446.                  DisplayIText->IText++;
  447.                  DisplayIText->LeftEdge += CHARW;
  448.               } else {
  449.                  DisplayIText->IText--;
  450.                  DisplayIText->LeftEdge -= CHARW;
  451.               }
  452.               DisplayValue = -DisplayValue;
  453.               RefreshDisplay();
  454.             }
  455.             break;
  456.  
  457.          case KEY_SQRT:
  458.             if (DisplayValue < 0.0)
  459.                DisplayError("Imaginary");
  460.               else
  461.                SetDisplay(sqrt(DisplayValue));
  462.             break;
  463.  
  464.          case KEY_PERCENT:
  465.             if (NoDigit == FALSE)
  466.             {
  467.                DisplayValue /= 100.0;
  468.                if (Op[StackTop] == OP_PLUS || Op[StackTop] == OP_MINUS)
  469.                   DisplayValue *= Val[StackTop];
  470.             }
  471.             SetDisplay(DisplayValue);
  472.             break;
  473.  
  474.          case KEY_CLEAR:
  475.             StackTop = 0;
  476.             ClearEntry();
  477.             break;
  478.  
  479.          case KEY_DISP:
  480.             if (NoDigit == FALSE) ClearEntry();
  481.             break;
  482.             
  483.          default:
  484.             if (NoDigit)
  485.             {
  486.                ClearDisplay();
  487.                if (AddToDisplay(theGadget->GadgetID+'0'))
  488.                   DisplayValue = (double)theGadget->GadgetID;
  489.             } else {
  490.                if (DecimalPlace != NODECIMAL)
  491.                {
  492.                   DecimalPlace++;
  493.                   DecimalPower *= 10.0;
  494.                   if (AddToDisplay(theGadget->GadgetID+'0'))
  495.                      DisplayValue += theGadget->GadgetID / DecimalPower;
  496.                } else {
  497.                   if (AddToDisplay(theGadget->GadgetID+'0'))
  498.                      DisplayValue = DisplayValue*10.0 + theGadget->GadgetID;
  499.                }
  500.             }
  501.             break;
  502.       }
  503.    }
  504. }
  505.  
  506.  
  507. /*
  508.  *  DoKey()
  509.  *
  510.  *  Check the key code for special characters:
  511.  *  CTRL-C means close the calculator (return FALSE).
  512.  *  ENTER key is converted to the equal sign (ENTER also equals RETURN).
  513.  *  Look through the gadget list for a gadget that has this character
  514.  *    in it's UserData field.  This is the gadget associated with this key code.
  515.  *  If one is found, then
  516.  *    complement the gadget that was "pressed"
  517.  *    do the gadget's function
  518.  *    delay for the key-press delay period (so that we can see the gadget
  519.  *      while it is complemented)
  520.  *    and un-complement the gadget.
  521.  */
  522.  
  523. int DoKey(Code)
  524. int Code;
  525. {
  526.    struct Gadget *theGadget = &CalcGadget[0];
  527.    int x,y,w,h;
  528.    
  529.    if (Code == CTRLC) return(FALSE);
  530.    if (Code == BACKSPACE && NoDigit == FALSE) ClearEntry();
  531.    if (Code == ENTER) Code = '=';
  532.  
  533.    while (theGadget && Code != (int)theGadget->UserData)
  534.       theGadget = theGadget->NextGadget;
  535.    if (theGadget)
  536.    {
  537.       SetDrMd(rp,COMPLEMENT);
  538.       x = theGadget->LeftEdge;
  539.       y = theGadget->TopEdge;
  540.       w = x + theGadget->Width - 1;
  541.       h = y + theGadget->Height - 1;
  542.       RectFill(rp,x,y,w,h);
  543.       DoGadget(theGadget);
  544.       Delay(KEYPAUSE);
  545.       RectFill(rp,x,y,w,h);
  546.    }
  547.    return(TRUE);
  548. }
  549.